package com.yyoo.doc.html.style.css;

import com.steadystate.css.parser.CSSOMParser;
import com.steadystate.css.parser.SACParserCSS3;
import com.yyoo.doc.html.style.StyleAttr;
import com.yyoo.doc.mypdf.MyPdfException;
import lombok.Data;
import org.w3c.css.sac.InputSource;
import org.w3c.dom.css.*;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

/**
 * @author zzy
 *
 * 使用 cssparser 解析 css 样式
 * 遵循 css 的优先级规则
 * 内联样式（style属性） > 内部样式表（style 标签） > 外部样式表（link 外部 .css 文件）
 */
@Data
public class CssParser {

    /**
     * 字符编码，默认 UTF-8
     */
    private Charset charSet = StandardCharsets.UTF_8;

    private static final String EROR_MSG = "css 解析错误";

    /**
     * 解析 css
     * @param css css 字符串
     * @return css 解析结果
     *
     * 解析结果 Map<选择器,Map<属性,值>>
     */
    public Map<String,Map<String,String>> parse(String css) {
        InputSource source = new InputSource(new StringReader(css));
        return parse(source);
    }

    /**
     * 解析 css
     * @param cssFilePath css 文件路径
     * @return css 解析结果
     *
     * 解析结果 Map<选择器,Map<属性,值>>
     */
    public Map<String,Map<String,String>> parse(Path cssFilePath) {
        try {
            InputSource source = new InputSource(Files.newBufferedReader(cssFilePath));
            return parse(source);
        } catch (IOException e) {
            throw new MyPdfException(EROR_MSG);
        }
    }

    /**
     * 解析远程 css
     * @param cssUrl 远程c ss 文件地址
     * @return css 解析结果
     *
     * 解析结果 Map<选择器,Map<属性,值>>
     */
    public Map<String,Map<String,String>> parse(URL cssUrl) {
        try {
            InputSource source = new InputSource(new InputStreamReader(cssUrl.openStream(), charSet));
            return parse(source);
        } catch (IOException e) {
            throw new MyPdfException(EROR_MSG);
        }
    }


    /**
     * 解析 css
     * @param source InputSource
     * @return css 解析结果
     *
     * 解析结果 Map<选择器,Map<属性,值>>
     */
    public Map<String,Map<String,String>> parse(InputSource source) {
        source.setEncoding(charSet.name());
        CSSOMParser parser = new CSSOMParser(new SACParserCSS3());

        CSSStyleSheet sheet;
        try {
            sheet = parser.parseStyleSheet(source, null, null);
        } catch (IOException e) {
            throw new MyPdfException(EROR_MSG);
        }

        Map<String,Map<String,String>> cssMap = new HashMap<>();

        CSSRuleList rules = sheet.getCssRules();

        for (int i = 0; i < rules.getLength(); i++) {
            final CSSRule rule = rules.item(i);
            if (rule instanceof CSSStyleRule) {
                final CSSStyleRule styleRule = (CSSStyleRule) rule;

                Map<String,String> styleMap = parseStyle(styleRule);
                // 如果选择器下支持的样式非空，则添加到结果中
                if(!styleMap.isEmpty()) {
                    final String selector = styleRule.getSelectorText();
                    addBySeletor(selector,cssMap,styleMap);
                }
            }
        }

        return cssMap;
    }

    /**
     * 将当前选择器对应的样式转换成 map
     * @param styleRule 当前选择器的样式对象
     * @return 将解析出的 css 样式规则映射为 Map
     */
    private Map<String,String> parseStyle(CSSStyleRule styleRule){
        final CSSStyleDeclaration styleDecl = styleRule.getStyle();

        Map<String,String> styleMap = new HashMap<>();
        for (int j = 0; j < styleDecl.getLength(); j++) {
            final String propName = styleDecl.item(j);
            // 只添加支持的样式
            if(StyleAttr.isSupportStyleKey(propName)) {
                styleMap.put(propName, styleDecl.getPropertyValue(propName));
            }
        }
        return styleMap;
    }

    /**
     * 将样式 styleMap 添加到以选择器为 key值的 cssMap
     * @param selector 选择器（多个并列的选择器将才分多个 map 存储）
     * @param cssMap 以选择器名为 key 的 map
     * @param styleMap 选择器对应的样式 map
     */
    private void addBySeletor(String selector,Map<String,Map<String,String>> cssMap,Map<String,String> styleMap){
        String[] selectors = selector.split(",");
        for (String s : selectors) {
            // 去除选择器前后的空格
            String selectorText = s.trim();
            cssMap.put(selectorText, styleMap);
        }
    }

}
